﻿using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using Microsoft.Xna.Framework;
using Microsoft.Xna.Framework.Graphics;
using Microsoft.Xna.Framework.Content;
using System.Xml;
using Nine;
using Nine.Animations;
using Nine.Graphics;
using Nine.Graphics.Effects;
using Nine.Graphics.Effects.EffectParts;
using Microsoft.Xna.Framework.Input;
using BEPUphysics;

namespace LevelEditor.EditorSupport
{
    public static class EditorComponents
    {
        public static Game Game;
        public static ContentManager Content;

        /// <summary>
        /// BEPU physics space.
        /// </summary>
        public static Space space;

        /// <summary>
        /// Display model for testing physics
        /// </summary>
        public static Model cube;

        public static TopDownEditorCamera Camera;

        public static BoundingBox CreateBoundingBox(Model model)
        {
            // Initialize minimum and maximum corners of the bounding box to max and min values
            Vector3 min = new Vector3(float.MaxValue, float.MaxValue, float.MaxValue);
            Vector3 max = new Vector3(float.MinValue, float.MinValue, float.MinValue);

            // For each mesh of the model
            foreach (ModelMesh mesh in model.Meshes)
            {
                foreach (ModelMeshPart meshPart in mesh.MeshParts)
                {
                    // Vertex buffer parameters
                    int vertexStride = meshPart.VertexBuffer.VertexDeclaration.VertexStride;
                    int vertexBufferSize = meshPart.NumVertices * vertexStride;

                    // Get vertex data as float
                    float[] vertexData = new float[vertexBufferSize / sizeof(float)];
                    meshPart.VertexBuffer.GetData<float>(vertexData);

                    // Iterate through vertices (possibly) growing bounding box, all calculations are done in world space
                    for (int i = 0; i < vertexBufferSize / sizeof(float); i += vertexStride / sizeof(float))
                    {
                        Vector3 transformedPosition = Vector3.Transform(new Vector3(vertexData[i], vertexData[i + 1], vertexData[i + 2]), mesh.ParentBone.Transform);

                        min = Vector3.Min(min, transformedPosition);
                        max = Vector3.Max(max, transformedPosition);
                    }
                }
            }

            // Create and return bounding box
            return new BoundingBox(min, max);
        }

        public static BoundingBox CreateBoundingBox(Model model, Matrix worldTransform)
        {
            // Initialize minimum and maximum corners of the bounding box to max and min values
            Vector3 min = new Vector3(float.MaxValue, float.MaxValue, float.MaxValue);
            Vector3 max = new Vector3(float.MinValue, float.MinValue, float.MinValue);

            // For each mesh of the model
            foreach (ModelMesh mesh in model.Meshes)
            {
                foreach (ModelMeshPart meshPart in mesh.MeshParts)
                {
                    // Vertex buffer parameters
                    int vertexStride = meshPart.VertexBuffer.VertexDeclaration.VertexStride;
                    int vertexBufferSize = meshPart.NumVertices * vertexStride;

                    // Get vertex data as float
                    float[] vertexData = new float[vertexBufferSize / sizeof(float)];
                    meshPart.VertexBuffer.GetData<float>(vertexData);

                    // Iterate through vertices (possibly) growing bounding box, all calculations are done in world space
                    for (int i = 0; i < vertexBufferSize / sizeof(float); i += vertexStride / sizeof(float))
                    {
                        Vector3 transformedPosition = Vector3.Transform(new Vector3(vertexData[i], vertexData[i + 1], vertexData[i + 2]), mesh.ParentBone.Transform * worldTransform);

                        min = Vector3.Min(min, transformedPosition);
                        max = Vector3.Max(max, transformedPosition);
                    }
                }
            }

            // Create and return bounding box
            return new BoundingBox(min, max);
        }

        /// <summary>
        /// Save a level. Read from a Z up level and write to a Y up file.
        /// </summary>
        /// <param name="stillModels"></param>
        public static void SaveLevel(List<EditorModels> stillModels, List<BoundingBox> eventBoxes, List<BoundingSphere> eventSpheres)
        {
            XmlTextWriter xWriter = new XmlTextWriter("GameLevel.xml", null);

            // Start the document
            xWriter.WriteStartDocument();
            xWriter.Formatting = Formatting.Indented;
            xWriter.Indentation = 1;
            xWriter.WriteComment("Created on " + DateTime.Now);
            xWriter.WriteStartElement("Level");

            // Write still models
            xWriter.WriteStartElement("StillModels");
            for (int i = 0; i < stillModels.Count; i++)
            {
                xWriter.WriteStartElement("Model");
                xWriter.WriteAttributeString("ID", stillModels[i].ID);
                xWriter.WriteAttributeString("GenerateMesh", stillModels[i].GenerateCollisionMesh.ToString());
                xWriter.WriteAttributeString("DisplayShadow", stillModels[i].DisplayShadow.ToString());
                xWriter.WriteAttributeString("DisplayModel", stillModels[i].DisplayModel.ToString());
                xWriter.WriteAttributeString("FileName", stillModels[i].FileName);
                xWriter.WriteAttributeString("Position", stillModels[i].Position.X + "," + stillModels[i].Position.Y + "," + stillModels[i].Position.Z);
                xWriter.WriteAttributeString("Rotation", stillModels[i].Rotation.X + "," + stillModels[i].Rotation.Y + "," + stillModels[i].Rotation.Z);
                xWriter.WriteAttributeString("Scale", stillModels[i].Scale.X + "," + stillModels[i].Scale.Y + "," + stillModels[i].Scale.Z);
                xWriter.WriteEndElement();
            }
            xWriter.WriteEndElement();

            // Write event boxes
            xWriter.WriteStartElement("EventBoxes");
            for (int i = 0; i < eventBoxes.Count; i++)
            {
                xWriter.WriteStartElement("Box");
                xWriter.WriteAttributeString("Min", eventBoxes[i].Min.X + "," + eventBoxes[i].Min.Y + "," + eventBoxes[i].Min.Z);
                xWriter.WriteAttributeString("Max", eventBoxes[i].Max.X + "," + eventBoxes[i].Max.Y + "," + eventBoxes[i].Max.Z);
                xWriter.WriteEndElement();
            }
            xWriter.WriteEndElement();

            // Write event spheres
            xWriter.WriteStartElement("EventSpheres");
            for (int i = 0; i < eventSpheres.Count; i++)
            {
                xWriter.WriteStartElement("Sphere");
                xWriter.WriteAttributeString("Center", eventSpheres[i].Center.X + "," + eventSpheres[i].Center.Y + "," + eventSpheres[i].Center.Z);
                xWriter.WriteAttributeString("Radius", eventSpheres[i].Radius.ToString());
                xWriter.WriteEndElement();
            }
            xWriter.WriteEndElement();

            // End the file
            xWriter.WriteEndElement();
            xWriter.WriteEndDocument();
            xWriter.Close();
        }

        /// <summary>
        /// Load a level, read in from a Y up file and swap to a Z up level.
        /// </summary>
        /// <param name="stillModels"></param>
        /// <param name="contentManager"></param>
        /// <param name="lightView"></param>
        /// <param name="lightProjection"></param>
        public static void LoadLevel(ref List<EditorModels> stillModels, ref List<BoundingBox> eventBoxes, ref List<BoundingSphere> eventSpheres, ContentManager contentManager, Matrix lightView, Matrix lightProjection)
        {
            XmlTextReader reader = new XmlTextReader("GameLevel.xml");

            try
            {
                reader.Read();
            }
            catch
            {
                return;
            }

            // Read the info
            while (reader.Read())
            {
                // Check we've found an element. Use a "switch" statement to access if we need more.
                if (reader.NodeType == XmlNodeType.Element)
                {
                    switch (reader.Name)
                    {
                        case "StillModels":
                            // Loop and load the models
                            while (reader.Read())
                            {
                                // Start loading our new text frame
                                if (reader.Name == "Model" && reader.NodeType == XmlNodeType.Element)
                                {
                                    string[] pos = reader["Position"].ToString().Split(',');
                                    string[] rot = reader["Rotation"].ToString().Split(',');
                                    string[] scale = reader["Scale"].ToString().Split(',');

                                    // Reading from a Z Up file
                                    stillModels.Add(new EditorModels(reader["ID"].ToString(), contentManager.Load<Model>(reader["FileName"].ToString()), contentManager.Load<Model>(reader["FileName"].ToString() + "_bepu"),
                                        Convert.ToBoolean(reader["GenerateMesh"].ToString()), Convert.ToBoolean(reader["DisplayShadow"].ToString()),
                                        Convert.ToBoolean(reader["DisplayModel"].ToString()), reader["FileName"].ToString(),
                                        new Vector3((float)Convert.ToDouble(pos[0]), (float)Convert.ToDouble(pos[1]), (float)Convert.ToDouble(pos[2])),
                                        new Vector3((float)Convert.ToDouble(scale[0]), (float)Convert.ToDouble(scale[1]), (float)Convert.ToDouble(scale[2])),
                                        new Vector3((float)Convert.ToDouble(rot[0]), (float)Convert.ToDouble(rot[1]), (float)Convert.ToDouble(rot[2]))));

                                    LoadShadowEffect(stillModels[stillModels.Count - 1].NineModel, "Effects/ShadowEffect", contentManager, lightView, lightProjection);
                                }

                                // Skip out of this set of models when we've reached the end
                                if (reader.Name == "StillModels" && reader.NodeType == XmlNodeType.EndElement)
                                {
                                    break;
                                }   // Exit StillModels case
                            }
                            break;  // Exit StillModels case
                        case "EventBoxes":
                            // Loop and load the boxes
                            while (reader.Read())
                            {
                                // Start loading our new box
                                if (reader.Name == "Box" && reader.NodeType == XmlNodeType.Element)
                                {
                                    string[] max = reader["Max"].ToString().Split(',');
                                    string[] min = reader["Min"].ToString().Split(',');

                                    eventBoxes.Add(new BoundingBox(new Vector3((float)Convert.ToDouble(min[0]), (float)Convert.ToDouble(min[1]), (float)Convert.ToDouble(min[2])),
                                        new Vector3((float)Convert.ToDouble(max[0]), (float)Convert.ToDouble(max[1]), (float)Convert.ToDouble(max[2]))));
                                }

                                // Skip out of this set of models when we've reached the end
                                if (reader.Name == "EventBoxes" && reader.NodeType == XmlNodeType.EndElement)
                                {
                                    break;
                                }   // Exit EventBoxes case
                            }
                            break;  // Exit EventBoxes case
                        case "EventSpheres":
                            // Loop and load the boxes
                            while (reader.Read())
                            {
                                // Start loading our new box
                                if (reader.Name == "Sphere" && reader.NodeType == XmlNodeType.Element)
                                {
                                    string[] max = reader["Center"].ToString().Split(',');

                                    eventSpheres.Add(new BoundingSphere(new Vector3((float)Convert.ToDouble(max[0]), (float)Convert.ToDouble(max[1]), (float)Convert.ToDouble(max[2])), (float)Convert.ToDouble(reader["Radius"].ToString())));
                                }

                                // Skip out of this set of models when we've reached the end
                                if (reader.Name == "EventSpheres" && reader.NodeType == XmlNodeType.EndElement)
                                {
                                    break;
                                }   // Exit EventBoxes case
                            }
                            break;  // Exit EventBoxes case
                    }
                }
            }

            reader.Close();
        }

        public static void LoadShadowEffect(Model model, string effect, ContentManager contentManager, Matrix lightView, Matrix lightProjection)
        {
            LinkedEffect shadow = contentManager.Load<LinkedEffect>(effect);
            shadow.EnableDefaultLighting();

            // Light view and light projection defines a light frustum.
            // Shadows will be projected based on this frustom.
            IEffectShadowMap shadowMapEffectPart = shadow.FindFirst<IEffectShadowMap>();

            // This value needs to be tweaked
            shadowMapEffectPart.DepthBias = 0.005f;
            shadowMapEffectPart.LightView = lightView;
            shadowMapEffectPart.LightProjection = lightProjection;

            model.ConvertEffectTo(shadow);
        }

        public static void DrawModel(Model model, Matrix worldTransform, Matrix[] absoluteBoneTransforms, ICamera camera, Vector3 specularColor)
        {
            // nothing tricky in here; this is the same model drawing code that we see
            // everywhere. we'll loop over all of the meshes in the model, set up their
            // effects, and then draw them.
            foreach (ModelMesh mesh in model.Meshes)
            {
                foreach (BasicEffect effect in mesh.Effects)
                {
                    effect.EnableDefaultLighting();
                    effect.PreferPerPixelLighting = false;
                    effect.SpecularColor = specularColor;
                    effect.DirectionalLight0.Direction = new Vector3(-1, -.5f, -1f);
                    effect.View = camera.View;
                    effect.Projection = camera.Projection;
                    effect.World = absoluteBoneTransforms[mesh.ParentBone.Index] * worldTransform;
                }
                mesh.Draw();
            }
        }
    }
}
